package com.example.demo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.exception.PlantException;
import com.example.demo.mapper.UMediaMapper;
import com.example.demo.mapper.UMeidaProcessMapper;
import com.example.demo.po.*;
import com.example.demo.service.UMediaService;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* @author 25418
* @description 针对表【u_media】的数据库操作Service实现
* @createDate 2023-06-07 18:00:02
*/
@Service
@Slf4j
public class UMediaServiceImpl extends ServiceImpl<UMediaMapper, UMedia>
    implements UMediaService {
    @Value("${minio.bucket.files}")
    private String bucket_mediafiles;
    @Value("${minio.bucket.videofiles}")
    private String bucket_video;
    @Autowired
    MinioClient minioClient;
    @Autowired
    UMediaMapper uMediaMapper;
    @Autowired
    UMeidaProcessMapper uMeidaProcessMapper;
    @Override
    public UploadFileResultDto upload(Integer contentId,String userId, UMedia uMedia, String localFilePath, String objectName) {
        //得到文件名
        String filename = uMedia.getFilename();
        //得到扩展名
        String substring = filename.substring(filename.lastIndexOf("."));
        //得到mimeType
        String mimeType = getMimeType(substring);
        //子目录
        String defaultFolderPath = getDefaultFolderPath();
        //文件的md5值
        String fileMd5 = getFileMd5(new File(localFilePath));
        if(StringUtils.isEmpty(objectName)){
            //使用默认年月日去存储
            objectName = defaultFolderPath+fileMd5+substring;
        }
        //上传文件到minio
        boolean result = addUMediaToMinIO(localFilePath, mimeType, bucket_mediafiles, objectName);
        if (!result){
            PlantException.cast("上传文件失败");
        }
        //入库文件信息
        UMedia uMedia1 = addUMediaToDb(contentId,userId, fileMd5, uMedia, bucket_mediafiles, objectName);
        if(uMedia1==null){
            PlantException.cast("文件上传后保存信息失败");
        }
        //准备返回的对象
        UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
        BeanUtils.copyProperties(uMedia1,uploadFileResultDto);
        return uploadFileResultDto;
    }


    private String getMimeType(String substring){
        if(substring == null){
            substring = "";
        }
        //根据扩展名取出mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(substring);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType，字节流
        if(extensionMatch!=null){
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }
    //获取文件默认存储目录路径 年/月/日
    private String getDefaultFolderPath() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String folder = sdf.format(new Date()).replace("-", "/")+"/";
        return folder;
    }
    //获取文件的md5
    private String getFileMd5(File file) {
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            String fileMd5 = DigestUtils.md5Hex(fileInputStream);
            return fileMd5;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public boolean addUMediaToMinIO(String localFilePath,String mimeType,String bucket, String objectName){
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)//桶
                    .filename(localFilePath) //指定本地文件路径
                    .object(objectName)//对象名 放在子目录下
                    .contentType(mimeType)//设置媒体文件类型
                    .build();
            //上传文件
            minioClient.uploadObject(uploadObjectArgs);
            log.debug("上传文件到minio成功,bucket:{},objectName:{}",bucket,objectName);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件出错,bucket:{},objectName:{},错误信息:{}",bucket,objectName,e.getMessage());
        }
        return false;
    }
    @Transactional
    public UMedia addUMediaToDb
            (Integer contentId,String userId,String fileMd5,UMedia uMedia,String bucket,String objectName){
        UMedia uMedia1 = uMediaMapper.selectById(fileMd5);
        if (uMedia1==null){
            uMedia1 = new UMedia();
            BeanUtils.copyProperties(uMedia,uMedia1);
            //文件id
            uMedia1.setId(fileMd5);
            //媒资绑定的页面
            uMedia1.setContentid(contentId);
            //上传人
            uMedia1.setUserid(userId);
            //桶
            uMedia1.setBucket(bucket);
            //path
            uMedia1.setPath(objectName);
            //fileId
            uMedia1.setFileid(fileMd5);
            //url
            uMedia1.setUrl("/"+bucket+"/"+objectName);
            //上传时间
            uMedia1.setCreateTime(LocalDateTime.now());
            //修改时间
            uMedia1.setUpdateTime(LocalDateTime.now());
            //审核意见
            uMedia1.setStatus(1);
            int insert = uMediaMapper.insert(uMedia1);
            if (insert<0){
                log.error("保存文件信息到数据库失败,{}", uMedia1);
                PlantException.cast("保存文件信息到数据库失败");
            }
            //添加到待处理任务表
            addWaitingTask(uMedia1);
            log.error("保存文件信息到数据库成功,{}", uMedia1);
        }
        return uMedia1;
    }
    private void addWaitingTask(UMedia uMedia) {
        //文件名称
        String filename = uMedia.getFilename();
        //文件扩展名
        String exension = filename.substring(filename.lastIndexOf("."));
        //文件mimeType
        String mimeType = getMimeType(exension);
        //如果是avi视频添加到视频待处理表
        if(mimeType.equals("video/x-msvideo")){
            UMeidaProcess uMeidaProcess = new UMeidaProcess();
            BeanUtils.copyProperties(uMedia,uMeidaProcess);
            uMeidaProcess.setStatus("1");//未处理
            uMeidaProcess.setFailCount(0);//失败次数默认为0
            uMeidaProcessMapper.insert(uMeidaProcess);
        }
    }
    @Override
    public PageResult queryMediaFiels(String userId, PageParams pageParams, QueryMediaParamsDto queryMediaParamsDto) {
        //构建查询条件对象
        LambdaQueryWrapper<UMedia> queryWrapper = new LambdaQueryWrapper<>();
        //分页对象
        Page<UMedia> page = new Page<>(pageParams.getPageNo(), pageParams.getPageSize());
        // 查询数据内容获得结果
        Page<UMedia> pageResult =uMediaMapper.selectPage(page, queryWrapper);
        // 获取数据列表
        List<UMedia> list = pageResult.getRecords();
        // 获取数据总数
        long total = pageResult.getTotal();
        // 构建结果集
        return new PageResult<>(list, total, pageParams.getPageNo(), pageParams.getPageSize());
    }

    @Override
    public RestResponse<Boolean> checkFile(String fileMd5,int chunk) {
        //得到分块文件目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath = chunkFileFolderPath + chunk;
        //定义文件流
        InputStream fileInputStream;
        try{
            fileInputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket_video)
                            .object(chunkFilePath)
                            .build()
            );
            if (fileInputStream!=null){
                return RestResponse.success(true);
            }
        }catch (Exception e){
        }
        return RestResponse.success(false);
    }

    @Override
    public RestResponse<Boolean> checkChunk(String fileMd5, int chunk) {
        //得到分块文件目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath = chunkFileFolderPath + chunk;
        //定义文件流
        InputStream fileInputStream;
        try{
            fileInputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket_video)
                            .object(chunkFilePath)
                            .build()
            );
            if (fileInputStream!=null){
                return RestResponse.success(true);
            }
        }catch (Exception e){
        }
        return RestResponse.success(false);
    }

    @Override
    public RestResponse uploadChunk(String fileMd5, int chunk, String localFilePath) {
        //得到分块文件的目录路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath = chunkFileFolderPath + chunk;
        //mimeType
        String mimeType = getMimeType(null);
        //将文件存储至minIO
        boolean b = addUMediaToMinIO(localFilePath, mimeType, bucket_video, chunkFilePath);
        if(!b){
            log.debug("文件上传失败:{}",chunkFilePath);
            return RestResponse.validfail("上传失败");
        }
        log.debug("上传分块文件成功:{}",chunkFilePath);
        return RestResponse.success("上传成功");
    }

    @Override
    public RestResponse mergechunks(Integer contentid, String id, String fileMd5, int chunkTotal, UMedia uMedia) {
        //1.获取分块文件路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //将分块路径组合成List<ComposeSource>
        List<ComposeSource> sourceObjectList = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> ComposeSource.builder()
                        .bucket(bucket_video)
                        .object(chunkFileFolderPath.concat(Integer.toString(i)))
                        .build())
                .collect(Collectors.toList());
        //2.合并
        //获取文件名称
        String fileName = uMedia.getFilename();
        //获取文件扩展名
        String extName = fileName.substring(fileName.lastIndexOf("."));
        //合并文件路径
        String mergeFilePath = getFilePathByMd5(fileMd5, extName);
        try {
            //合并文件
            ObjectWriteResponse response = minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucket_video)
                            .object(mergeFilePath)
                            .sources(sourceObjectList)
                            .build());
            log.debug("合并文件成功:{}",mergeFilePath);
        }catch (Exception e) {
            log.debug("合并文件失败,fileMd5:{},异常:{}",fileMd5,e.getMessage(),e);
            return RestResponse.validfail(false,"合并文件异常");
        }
        //3.验证文件
        //下载合并的文件
        File file = downloadFileFromMinIO(bucket_video, mergeFilePath);
        try(FileInputStream fileInputStream = new FileInputStream(file)){
            //计算合并后文件的md5
            String mergeFile_md5 = DigestUtils.md5Hex(fileInputStream);
            //比较原始md5和合并后文件的md5
            if(!fileMd5.equals(mergeFile_md5)){
                log.error("校验合并文件md5值不一致,原始文件:{},合并文件:{}",fileMd5,mergeFile_md5);
                return RestResponse.validfail(false,"文件校验失败");
            }
            //文件大小
            uMedia.setFilesize(file.length());
        }catch (Exception e) {
            return RestResponse.validfail(false,"文件校验失败");
        }

        //==============将文件信息入库============
        UMedia uMedia1 = addUMediaToDb
                (contentid,id, fileMd5, uMedia, bucket_video, mergeFilePath);
        if(uMedia1 == null){
            return RestResponse.validfail(false,"文件入库失败");
        }
        //==========清理分块文件=========
        clearChunkFiles(chunkFileFolderPath,chunkTotal);

        return RestResponse.success(true);
    }
    @Override
    public File downloadFileFromMinIO(String bucket,String objectName){
        //临时文件Me
        File minioFile = null;
        FileOutputStream outputStream = null;
        try{
            InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .build());
            //创建临时文件
            minioFile=File.createTempFile("minio", ".merge");
            outputStream = new FileOutputStream(minioFile);
            IOUtils.copy(stream,outputStream);
            return minioFile;
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
    private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){
        Iterable<DeleteObject> objects =  Stream.iterate(0, i -> ++i).limit(chunkTotal).map(i -> new DeleteObject(chunkFileFolderPath+ i)).collect(Collectors.toList());;
        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(bucket_video).objects(objects).build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        //要想真正删除
        results.forEach(f->{
            try {
                DeleteError deleteError = f.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

    }
    private String getFilePathByMd5(String fileMd5,String fileExt){
        return   fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
    }
    private String getChunkFileFolderPath(String fileMd5) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
    }
}




